Repository navigation
Read an accepted update's outcome later by update id (#212) - #309
Conversation
Client.update_outcome() and update_outcome_receipt() call
GET /workflows/{workflow_id}/updates/{update_id} (server#1131) on a
Server that advertises capabilities.update_outcome_reads. They never
resubmit the update: without the capability they raise
RuntimeCapabilityUnsupported after discovery. A pending read raises the
new UpdateStillPending (never a None result) with a read-again-by-id
hint; a failed update raises UpdateFailed, a rejected one
UpdateRejected, and 404 update_not_found the new UpdateNotFound
(an UpdateFailed). WorkflowHandle and the sync client/handle get the
same methods. update_workflow's docstring now states that it returns
the receipt and that the Server defaults to wait_for=accepted.
Part of durable-workflow/.github#212.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Security PASS at d9d9349, with a recommended follow-up. It's GET only with no body. The transport retries can only repeat the GET, so nothing resubmits the update. It uses the same Recommended, matching the Rust change request: refuse empty, Non-blocking:
|
Refuse an empty, "." or ".." workflow or update id (and an invalid wait_timeout_seconds) before any request, so ".." cannot resolve onto the workflow describe route. A 2xx answer must name the requested update_id, carry a string update_status and match an echoed workflow_id; update_outcome() returns a value only from a completed receipt. Anything else raises the new UnrecognizedUpdateOutcome. A wait_timeout_seconds read extends the HTTP timeout to the wait plus 30 s when that exceeds the client timeout. Part of durable-workflow/.github#212. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
Security PASS at d655753, on green CI. My recommendations are in: empty, |



Part of durable-workflow/.github#212 (the sdk-python slice). Builds on the native read route from server#1131 (native 3.6.0), and follows the PHP reference in sdk-php 3.15.0.
What changed
Client.update_outcome(workflow_id, update_id, *, wait_for="completed", wait_timeout_seconds=None, result_type=None)reads an accepted update by id withGET /api/workflows/{workflow_id}/updates/{update_id}and returns the decoded result, includingNone.update_status: "accepted", with or withoutwait_timed_out) raises the newUpdateStillPending. It carriesworkflow_id,run_id,update_name,update_id,request_id,update_status,wait_timed_outandbody, and its message says to callupdate_outcome()again with the update id. A pending update is never returned asNone.UpdateFailed. A rejected update keeps its original rejection status and raisesUpdateRejected, as submitting it does. A 404update_not_foundraises the newUpdateNotFound, a subclass ofUpdateFailed, so it is not mistaken forWorkflowNotFound.Client.update_outcome_receipt(..., wait_for="accepted", ...)returns the raw receipt.capabilities.update_outcome_reads === truein cluster info. If it is missing or false, they raiseRuntimeCapabilityUnsupportednaming that path. Only the discovery request is made: nothing is read and nothing is resubmitted. Both methods also check thatwait_forisacceptedorcompletedand that the update id is not empty, before sending any request.wait_for=completedandwait_timeout_secondsgo in the query string. The route/workflows/{workflow_id}/updates/{update_id}is added to the bounded metrics route registry.WorkflowHandle.update_outcome()/update_outcome_receipt()and the syncClient/SyncWorkflowHandlewrappers are added.update_workflow. Its return type is unchanged; it still returns the receipt. Its docstring (andWorkflowHandle.update's) now says it returns the receipt and not the decoded result, that the Server answers atacceptedwhenwait_foris omitted, how to tell pending from a completedNone, and thatupdate_outcome()reads the result later. No existing code path decoded update results, so no pending-to-Nonecollapse needed fixing.docs/sdk-reference.mdare updated.Tests
tests/test_update_outcome.pyuseshttpx.MockTransportand covers:Noneresult_typedecodingupdate_not_found(404), and rejected updates (409/404/422)wait_forand update-id validation, with no requests sentLocally in Docker on Python 3.12: ruff, mypy, the Avro benchmark gate, the full unit suite (5945 passed), the regression-corpus policy, and the package build/metadata/smoke checks all pass.
🤖 Generated with Claude Code